// (c) Copyright 2013-2014 Stephen Gold <sgold@sonic.net>
// Distributed under the terms of the GNU General Public License

/*
 This file is part of the Bats Game.

 The Bats Game is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by the
 Free Software Foundation, either version 3 of the License, or (at your
 option) any later version.

 The Bats Game is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 for more details.

 You should have received a copy of the GNU General Public License
 along with the Bats Game.  If not, see <http://www.gnu.org/licenses/>.
 */
package bats.unit;

import bats.Bats;
import bats.SpatialProperties;
import bats.scene.SceneNode;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import java.util.logging.Logger;
import jme3utilities.MySpatial;
import jme3utilities.SimpleControl;
import jme3utilities.math.VectorXZ;
import jme3utilities.ui.Signals;

/**
 * A simple control to guide a unit using command signals generated by buttons
 * and/or hotkeys.
 * <p>
 * Each instance is enabled at creation.
 *
 * @author Stephen Gold <sgold@sonic.net>
 */
public class SignalControl
        extends SimpleControl {
    // *************************************************************************
    // constants

    /**
     * meters per kilometer
     */
    final private static float km = 1000f;
    /**
     * rotation speed in pose mode (in radians/second, &gt;0)
     */
    final private static float poseTurnRate = 2f;
    /**
     * message logger for this class
     */
    final private static Logger logger =
            Logger.getLogger(SignalControl.class.getName());
    /**
     * rotation vector for backward movement
     */
    final private static VectorXZ backward = new VectorXZ(-1f, 0f);
    /**
     * rotation vector for forward movement
     */
    final private static VectorXZ forward = new VectorXZ(1f, 0f);
    /**
     * rotation vector for the unit's left
     */
    final private static VectorXZ toTheLeft = new VectorXZ(0f, -1f);
    /**
     * rotation vector for the unit's right
     */
    final private static VectorXZ toTheRight = new VectorXZ(0f, 1f);
    // *************************************************************************
    // fields
    /**
     * type of unit: set by constructor
     */
    final private UnitClass units;
    // *************************************************************************
    // constructors

    /**
     * Instantiate an enabled control.
     *
     * @param units which type of unit (not null)
     */
    public SignalControl(UnitClass units) {
        assert units != null;
        this.units = units;

        assert isEnabled();
    }
    // *************************************************************************
    // SimpleControl methods

    /**
     * Callback invoked when the spatial's geometric state is about to be
     * updated, once per frame while attached and enabled.
     *
     * @param updateInterval time interval between updates (in seconds, &ge;0)
     */
    @Override
    protected void controlUpdate(float updateInterval) {
        super.controlUpdate(updateInterval);
        if (spatial == null) {
            return;
        }
        /*
         * In sim mode, take no action unless the unit is picked.
         */
        SceneNode scene = SceneNode.find(spatial);
        boolean simMode = Bats.isSimulationMode();
        if (simMode && !SpatialProperties.isPicked(spatial)) {
            return;
        }
        MoveControl moveControl = units.getMoveControl(spatial);
        assert moveControl != null;
        /*
         * Tell the move control to forget all prior guidance.
         */
        moveControl.newGuidanceInput();
        /*
         * Get the unit's walk direction.
         */
        VectorXZ walkDirection = units.getMapPelvicDirection(spatial);
        /*
         * Get the horizontal direction which is to the camera's left.
         */
        Vector3f worldLeft = scene.getApplication().getCamera().getLeft();
        VectorXZ leftDirection = new VectorXZ(worldLeft);
        /*
         * Rotate it so that +X is advancing, +Z is unit's right.
         */
        leftDirection = leftDirection.rotate(walkDirection.mirrorZ());

        Signals signals = scene.getApplication().getSignals();
        if (!simMode) {
            /*
             * In pose mode, only rotation is allowed.
             */
            float turnRate = 0f; // radians per second
            if (signals.test("turnLeft")) {
                turnRate -= poseTurnRate;
            }
            if (signals.test("turnRight")) {
                turnRate += poseTurnRate;
            }
            float turnAngle = turnRate * updateInterval;
            Quaternion rotation = new Quaternion();
            rotation.fromAngleNormalAxis(turnAngle, Vector3f.UNIT_Y);
            MySpatial.rotateObject(spatial, Vector3f.ZERO, rotation);
            return;
        }

        if (signals.test("advance")) {
            requestLargeMove(forward);
        }
        LaunchControl launchControl = spatial.getControl(LaunchControl.class);
        if (signals.test("loadLaunch") && launchControl != null) {
            Spatial missile = launchControl.getMissile();
            float elevationAngle = 0f;
            if (missile == null) {
                launchControl.requestLoad(elevationAngle);
            } else if (launchControl.isReadyToLaunch()) {
                Otos otos = scene.getWorld().getOtos();
                float launchSpeed = otos.getMaxLaunchSpeed(spatial); // m/s
                launchControl.requestLaunch(launchSpeed, elevationAngle);
            }
        }
        if (signals.test("retreat")) {
            requestLargeMove(backward);
        }
        if (signals.test("stepLeft")) {
            requestLargeMove(toTheLeft);
        }
        if (signals.test("stepRight")) {
            requestLargeMove(toTheRight);
        }
        if (signals.test("stepViewBottom")) {
            /*
             * step to the bottom of the view
             */
            requestLargeMove(leftDirection.getZ(), -leftDirection.getX());
        }
        if (signals.test("stepViewLeft")) {
            /*
             * step toward the camera's left
             */
            requestLargeMove(leftDirection.getX(), leftDirection.getZ());
        }
        if (signals.test("stepViewRight")) {
            /*
             * step toward the camera's right
             */
            requestLargeMove(-leftDirection.getX(), -leftDirection.getZ());
        }
        if (signals.test("stepViewTop")) {
            /*
             * step toward the top of the view
             */
            requestLargeMove(-leftDirection.getZ(), leftDirection.getX());
        }
        if (signals.test("turnLeft")) {
            moveControl.requestTurn(toTheLeft);
        }
        if (signals.test("turnRight")) {
            moveControl.requestTurn(toTheRight);
        }
    }
    // *************************************************************************
    // private methods

    /**
     * Request a large horizontal move relative to the unit's position.
     *
     * @param forward desired forward movement (in kilometers)
     * @param rightward desired rightward movement (in kilometers)
     */
    private void requestLargeMove(float forward, float rightward) {
        MoveControl moveControl = units.getMoveControl(spatial);
        moveControl.requestMove(forward * km, rightward * km);
    }

    /**
     * Request a large horizontal move relative to the unit's position.
     *
     * @param vector desired movement (in kilometers)
     */
    private void requestLargeMove(VectorXZ vector) {
        requestLargeMove(vector.getX(), vector.getZ());
    }
}